﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Modeling;
using System.ComponentModel.Composition;
using VsxFactory.Modeling.VisualStudio;
using VsxFactory.Modeling.Strategies.Rules;

namespace VsxFactory.Modeling.Strategies
{
    /// <summary>
    /// Stratégies utilisant des règles de génération
    /// </summary>
    public abstract class CustomDslStrategy : ModelingStrategy  //, IStrategyGenerateArtifacts
    {
        IList<GenerationRule> rules = null;

        protected CustomDslStrategy()
        {

        }

        public IList<GenerationRule> Rules
        {
            get
            {
                if (rules == null)
                {
                    rules = GetRules();
                    if (rules == null)
                        throw new Exception("Rules list must not be null");
                }
                return rules;
            }
        }

        protected abstract IList<GenerationRule> GetRules();

        protected override bool Generate(IStrategyContext ctx, bool force=false)
        {
            if (ctx.Element == null)
                return false;
            GenerationSpec generationElementWrapper = CreateGenerationSpec(ctx, ctx.Element);
            if (generationElementWrapper == null)
                return false;

            bool flag = false;
            foreach (var rule in Rules)
            {
                flag |= PerformRule(ctx, generationElementWrapper, rule);
            }
            return flag;
        }

        private bool PerformRule(IStrategyContext ctx, GenerationSpec generationElementWrapper, GenerationRule rule)
        {
            if (rule.IsConditionTrue(ctx, generationElementWrapper))
            {
                var generationEvent = ctx.GenerationEvent;
                var targetElement = rule.TargetElement(generationElementWrapper);
                    if( (generationEvent & CodeGenerationEvent.OnModelElementAdded) == CodeGenerationEvent.OnModelElementAdded )
                    {
                        ctx.WriteLogMessage("Apply rule {0} for added element {1}", rule.Name, generationElementWrapper.Name);
                        rule.Generate(ctx, targetElement, null);
                    }
                    if ((generationEvent & CodeGenerationEvent.OnModelElementPropertyChanged) == CodeGenerationEvent.OnModelElementPropertyChanged)
                    {
                        if (ShouldGenerateCodeOnPropertyChange(rule, ctx, generationElementWrapper))
                        {
                            if (rule.MustDeleteOldGeneratedArtefactOnPropertyChange(ctx, generationElementWrapper))
                            {
                                // Force la suppression ==> Ne pas modifier l'emplacement du test sur OnModelElementRemoved ci-dessous
                                generationEvent = CodeGenerationEvent.OnModelElementRemoved;
                            }
                            else
                            {
                                string propertyName = ctx.Element.Store.DomainDataDirectory.GetDomainProperty(ctx.PropertyId).Name;
                                ctx.WriteLogMessage("Apply rule {0} for property change {1} on element {2}", rule.Name, propertyName, generationElementWrapper.Name);
                                rule.Change(ctx, generationElementWrapper, targetElement, ctx.PropertyId, ctx.OldPropertyValue);
                            }
                        }
                    }
                    if ((generationEvent & CodeGenerationEvent.OnModelElementRemoved) == CodeGenerationEvent.OnModelElementRemoved)
                    {
                        ctx.WriteLogMessage("Apply rule {0} for removed element {1}", rule.Name, generationElementWrapper.Name);
                        rule.Remove(ctx, targetElement);
                    }
         
                if (rule.NestedRules != null)
                {
                    foreach (var nestedRule in rule.NestedRules)
                    {
                        PerformRule(ctx, generationElementWrapper, nestedRule);
                    }
                }

                rule.PostGenerationAction(ctx, generationElementWrapper);
                return true;
            }
            return false;
        }

        protected virtual bool ShouldGenerateCodeOnPropertyChange(GenerationRule rule, IStrategyContext ctx, GenerationSpec generationElementWrapper)
        {
            return rule.ShouldGenerateCodeOnPropertyChange(ctx, generationElementWrapper);
        }

        public string Name
        {
            get { return this.GetType().Name; }
        }
    }
}
